/***************************************************************************
 *   Copyright (C) 2006 by Harish Kukreja                                  *
 *   harish.kukreja@gmail.com                                              *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

//
// C++ Interface: world
//
// Description: Definitions for Physiks' 'World' class and its members
//
//
// Author: Harish Kukreja <harish.kukreja@gmail.com>, (C) 2006
//
// Copyright: See COPYING file that comes with this distribution
//
//


#include <physiksbase/world.h>
#include <physiksbase/globaldefs.h>

using PhysiksBase::World;
using PhysiksBase::PhysicalObject;
using namespace PhysiksBase::Computation;
using std::vector;
using std::cout;

// A whole bunch of overloaded World constructors -----------------------------
World::World(int dim, double tstep) :
        output(false), objects(0), force_fields(0), dimension(dim), timestep(tstep) {
}

World::World(int dim, double tstep, vector<PhysicalObject *> &init_obj) : output(false), objects(init_obj), force_fields(0), dimension(dim), timestep(tstep), time(0.0) {
}

World::World(int dim, double tstep, vector<PhysicalObject *> &init_obj, vector<ForceField *> &init_force_fields) : output(false), objects(init_obj), force_fields(init_force_fields), dimension(dim), timestep(tstep), time(0.0) {
}

//-----------------------------------------------------------------------------

World::~World() {}

void World::add_object(PhysicalObject &obj) {
    objects.push_back(&obj);
}

// Add a unary procedure with side-effects to the list of such procedures in the calling world.
void World::add_single_object_proc(void (*proc)(PhysicalObject *)) {
    single_object_procs.push_back(proc);
}

// Add a binary procedure with side-effects to the list of such procedures in the calling world.
void World::add_double_object_proc(void (*proc)(PhysicalObject *, PhysicalObject *)) {
    double_object_procs.push_back(proc);
}

void World::integrate(double start_time, double end_time) {
    this->time = start_time;
    while (this->time < end_time) {
        if (output) print_data();
        step();
    }
}

void World::step() {
    Computation::set_force_vectors_zero(objects);
    Computation::apply_force_fields(objects, force_fields);
    Computation::apply_acceleration_fields(objects, acceleration_fields);
    Computation::apply_velocity_fields(objects, velocity_fields);
    apply_user_defined_fields();
    apply_user_defined_procs();
    Computation::update_forces(objects);
    Computation::update_velocities(objects, timestep);
    Computation::update_positions(objects, timestep);

    this->time += timestep;
}

double World::current_time() const {
    return this->time;
}

PhysicalObject *World::object(unsigned int index) const {
    return objects[index];
}

unsigned int World::num_objects() const {
    return objects.size();
}

// Applies all user defined procedures passed to the world, to all active objects within the world.
void World::apply_user_defined_procs() {
    if (!single_object_procs.empty()) {
        for (uint i = 0; i < objects.size(); i++) (*single_object_procs[i])(objects[i]);
    }
    if (!double_object_procs.empty()) {
        for (uint i = 0; i < double_object_procs.size(); i++)
            for (uint j = 0; j < objects.size(); j++)
                for (uint k = j + 1; k < objects.size(); k++)
                    (*double_object_procs[i])(objects[j], objects[k]);
    }
}

void World::apply_user_defined_fields() {
    Vector temp(dimension, 0.0);
    if (!force_fields.empty())
        for (uint i = 0; i < force_fields.size(); i++)
            for (uint k = 0; k < objects.size(); k++) {
                temp = (*force_fields[i])(objects[k]->position);
                objects[k]->net_force += temp;
            }
// Incorporate electric and magnetic fields.
}

// Output needs to be clearer. Essential object distinguishers are omitted due to
// laziness.
void World::print_data() {
    for (uint i = 0; i < objects.size(); i++) {
        cout << time << ": " << objects[i]->net_force.core()->data[0] << ", " <<
                objects[i]->net_force.core()->data[1] << ", " <<
                objects[i]->net_force.core()->data[2] << "\n";
        cout << time << ": " << objects[i]->velocity.core()->data[0] << ", " <<
                objects[i]->velocity.core()->data[1] << ", " <<
                objects[i]->velocity.core()->data[2] << "\n";
        cout << time << ": " << objects[i]->position.core()->data[0] << ", " <<
                objects[i]->position.core()->data[1] << ", " <<
                objects[i]->position.core()->data[2] << "\n";
    }
}
